#include "SimulationWorkFlow.h"
#include <vector>
#include "User.h"
#include "Events.h"
#include "Event.h"
#include "Market.h"
#include "AppLogger.h"
#include "Reading.h"
#include <string>
#include <iostream>
#include <sstream>
using namespace std;
using namespace Poco;
using namespace ass2;

SimulationWorkFlow::SimulationWorkFlow(vector<User*> users_, Events* events_,
		bool sayaON_, int sayaBuyingAmount_,string configFile,
		string eventFile, double price)
{
	this -> marketReport = CMarketReport(configFile,eventFile,37101730,34647727);
	this->market = new Market(users_, price, sayaON_);
	this->events = events_;
	this -> users = users_;
	sayaON = sayaON_;
	sayaBuyingAmount = sayaBuyingAmount_;
	bool found = false;
	if (sayaON)
	{
		vector<User*>::iterator it;
		for(it = users.begin();!found&&it<users.end();it++)
		{
			User* user = *it;
			if(user->getName()=="eSaya")
			{
				found = true;
				Saya = user;
			}
		}
	}

}

SimulationWorkFlow::~SimulationWorkFlow()
{
	delete market;
}


//void SimulationWorkFlow::sellANDbuying()
//{
//
//}


//bool SimulationWorkFlow::isThereEventsLeft()
//{
//
//}



void SimulationWorkFlow::run()
{
	while (!events->noMoreEvents())
	{
		DateTime today = events ->getCorrentDay();
		this -> doAlltheCreateEventsForToday(today);
		this -> doAlltheConsumingEventsForToday(today);
		this -> makeAllDealsForToday(today);
		if (sayaON)
		{
			this -> createSayaEvents(today);
		}
		this -> updateUserProfit();
		this -> doReportsEvent(today);
	}
}

void SimulationWorkFlow::updateUserProfit()
{
	vector<User*>::iterator it;
	for (it = users.begin();it <users.end();it++)
	{
		User* user = *it;
		user -> updateProfit();
	}
}

void SimulationWorkFlow::doReportsEvent(DateTime today)
{
	Event report = this -> events ->getReportingEvent(today);
	while (!report.isEmpty())
	{
		if (report.getType() == "userReport")
			this -> doUserReport(report);
		if (report.getType() == "energyReport")
			this -> doEnergyReport(report);
		if (report.getType() == "accountReport")
			this -> doAccountReport(report);
		if (report.getType() == "profitReport")
			this -> doProfitReport(report);
		if (report.getType() == "activityReport")
			this -> doActivityReport(report);
		CAppLogger::Instance().Log(report.getType() + " report was generated",Message::PRIO_NOTICE);
		report = this -> events ->getReportingEvent(today);
	}
}

void SimulationWorkFlow::doUserReport(Event report)
{
	User& user = this -> getUserbyID(report.getUser());
	int ID = this -> stringToInt(report.getUser());
	unitContainer energyUnits;
	energyUnits.coal = user.getEnergyStatus("coal");
	energyUnits.gas = user.getEnergyStatus("gas");
	energyUnits.hug = user.getEnergyStatus("hug");
	energyUnits.oil = user.getEnergyStatus("oil");
	energyUnits.smile = user.getEnergyStatus("smile");
	energyUnits.sun = user.getEnergyStatus("sun");
	energyUnits.wind = user.getEnergyStatus("wind");
	marketReport.reportUser(ID,user.getName(),energyUnits,report.getDate());


}


void SimulationWorkFlow::doEnergyReport(Event report)
{
	valueContainer value;
	value.coal = market -> getEnergyPrice("coal");
	value.gas = market -> getEnergyPrice("gas");
	value.hug = market -> getEnergyPrice("hug");
	value.oil = market -> getEnergyPrice("oil");
	value.smile = market -> getEnergyPrice("smile");
	value.sun = market -> getEnergyPrice("sun");
	value.wind = market -> getEnergyPrice("wind");
	marketReport.reportEnergy(&value,report.getDate());
}



void SimulationWorkFlow::doProfitReport(Event report)
{
	User& user = this -> getUserbyID(report.getUser());
	int ID = this -> stringToInt(report.getUser());
	marketReport.reportProfit(ID,user.getName(),user.getProfit(),report.getDate());
}


void SimulationWorkFlow::doActivityReport(Event report)
{
	User& user = this -> getUserbyID(report.getUser());
	unitContainer create,consume;
	create.coal = user.getCreateStatus("coal");
	create.gas = user.getCreateStatus("gas");
	create.hug = user.getCreateStatus("hug");
	create.oil = user.getCreateStatus("oil");
	create.smile = user.getCreateStatus("smile");
	create.sun = user.getCreateStatus("sun");
	create.wind = user.getCreateStatus("wind");
	consume.coal = user.getConsumingSatus("coal");
	consume.gas = user.getConsumingSatus("gas");
	consume.hug = user.getConsumingSatus("hug");
	consume.oil = user.getConsumingSatus("oil");
	consume.smile = user.getConsumingSatus("smile");
	consume.sun = user.getConsumingSatus("sun");
	consume.wind = user.getConsumingSatus("wind");
	//cout<<"another print try "<<user.getEnergyStatus("oil")<<endl;
	//cout<<"the oil i created is "<< create.oil<<endl;
	marketReport.reportActivity(user.getName(),create,consume,report.getDate());
}


void SimulationWorkFlow::doAccountReport(Event report)
{
	User& user = this -> getUserbyID(report.getUser());
	int ID = this -> stringToInt(report.getUser());
	marketReport.reportAccount(ID,user.getName(),user.getUserStatus(),report.getDate());
}


User& SimulationWorkFlow::getUserbyID(string ID)
{
	User* user=0;
	vector<User*>::iterator it;
	for (it = users.begin();it <users.end();it++)
	{
		user = *it;
		if (user-> getID()== ID)
			return *user;
	}
	return *user;

}


void SimulationWorkFlow::makeAllDealsForToday(DateTime today)
{
	for(eEnergy i = coal ; i<noEnergy;++i)
	{
		string energyType = energyToString(i);
		Event buyer = events -> getBuyingEvent(energyType,today);
		Event seller = events -> getSellingEvent(energyType,today);
		while (!buyer.isEmpty() & !seller.isEmpty() & buyer.getPrice() >= seller.getPrice())
		{
			while (!seller.isEmpty() && !this -> validSeller(seller.getAmount(),seller.getUser(),energyType))
			{
				seller = events -> getSellingEvent(energyType,today);
			}
			if (!seller.isEmpty())
			{
				int energyAmount = min(seller.getAmount(),buyer.getAmount());
				double priceOfTheDeal = ((buyer.getPrice()+seller.getPrice())/2)*energyAmount*1.005;
				while (!buyer.isEmpty()&&(!this ->validBuyer(priceOfTheDeal,buyer.getUser()))&&
						!this -> validBuyer(buyer.getPrice()*buyer.getAmount(),buyer.getUser()))
				{
					User& user = this -> getUserbyID(buyer.getUser());
					CAppLogger::Instance().Log("User " + user.getName() + " dosen't have enough money" ,Message::PRIO_ERROR);
					buyer = events -> getBuyingEvent(energyType,today);   //need to check somsthing about buyer and seller
					energyAmount = min(seller.getAmount(),buyer.getAmount());
					priceOfTheDeal = ((buyer.getPrice()+seller.getPrice())/2)*energyAmount*1.005;
				}
			}
			if (!buyer.isEmpty()&!seller.isEmpty()&buyer.getPrice()>=seller.getPrice())
			{
				//this ->makeDeal(buyer,seller);
				market->deal(buyer,seller);

				if (buyer.getAmount() > seller.getAmount())
				{
					buyer.UpdateEvent(buyer.getAmount()-seller.getAmount());
					seller = events -> getSellingEvent(energyType,today);
				}
				else if (seller.getAmount() > buyer.getAmount())
				{
					seller.UpdateEvent(seller.getAmount() - buyer.getAmount());
					buyer = events -> getBuyingEvent(energyType,today);
				}
				else
				{
					buyer = events -> getBuyingEvent(energyType,today);
					seller = events -> getSellingEvent(energyType,today);
				}
			}
		}
	}
}


bool SimulationWorkFlow::validBuyer(double price,string userID)
{
	vector<User*>::iterator it;
	bool found = false;
	bool ans = false;
	for(it = users.begin();it<users.end()& !found; it++)
	{
		User* user = *it;
		if (userID == user ->getID()) //i think its wrong because in event its is id and here it is name but in user id its int
		{
			found = true;
			if (user -> getUserStatus()>= price)
			{
				ans = true;
			}
//			else
//			{
//				CAppLogger::Instance().Log("User " + user->getName() + " dosen't have enough money" ,Message::PRIO_ERROR);
//			}
		}
	}
	return ans;
}

bool SimulationWorkFlow::validSeller(int amount,string userID,string energyType)
{
	vector<User*>::iterator it;
	bool found = false;
	bool ans = false;
	for(it = users.begin();it<users.end()& !found; it++)
	{
		User* user = *it;
		if (userID == user ->getID()) //i think its wrong because in event its is id and here it is name but in user id its int
		{
			found = true;
			if (user -> getEnergyStatus(energyType) >= amount)
			{
				ans = true;
			}
			else
			{
				CAppLogger::Instance().Log("User " + user->getName() + " dosen't have enough Energy Units" ,Message::PRIO_ERROR);
			}
		}
	}
	return ans;
}


void SimulationWorkFlow::doAlltheCreateEventsForToday(DateTime today)
{
	for(eEnergy i = coal ; i<noEnergy;++i)
	{
		string energyType = energyToString(i);
		Event create = events -> getCreatingEvent(energyType,today);
		while (!create.isEmpty())
		{
			this -> doCreateEvent(create,energyType);
			create = (events -> getCreatingEvent(energyType,today));
		}
	}

}

void SimulationWorkFlow::doCreateEvent(Event& event,string energyType)
{
	vector<User*>::iterator it;
	bool found = false;
	for(it = users.begin();it<users.end()& !found; it++)
	{
		User* user = *it;
		if (event.getUser() == user ->getID()) //i think its wrong because in event its is id and here it is name but in user id its int
		{
			found = true;
			user -> creat(energyType,event.getAmount());
			market -> addEnergyAmount(energyType,event.getAmount());
			// adding to the total energyis in the market.
		}
	}
}

void SimulationWorkFlow::doAlltheConsumingEventsForToday(DateTime today)
{
	for(eEnergy i = coal ; i<noEnergy;++i)
	{
		string energyType = energyToString(i);
		Event consume = events -> getConsumingEvent(energyType,today);
		while (!consume.isEmpty())
		{
			this -> doConsumeEvent(consume,energyType);
			consume = events -> getConsumingEvent(energyType,today);
		}
	}
}
void SimulationWorkFlow::doConsumeEvent(Event& event,string energyType)
{
	vector<User*>::iterator it;
	bool found = false;
	for(it = users.begin();it<users.end()& !found; it++)
	{
		User* user = *it;
		if (event.getUser() == user ->getID()) //i think its wrong because in event its is id and here it is name but in user id its int
		{
			found = true;
			if (user -> getEnergyStatus(energyType) - event.getAmount()>=0) // need to check because getEnergyStatus return double
			{
				user -> consume(energyType,event.getAmount());
				market -> removeEnergyAmount(event.getEnergyType(),event.getAmount());
			}
		}
	}
}
void SimulationWorkFlow::createSayaEvents(DateTime today)
{
	for(eEnergy i = coal ; i<noEnergy;++i)
	{
		string energyType = energyToString(i);
		if(market->SayaCreateSellingEvent(energyType))
		{
			Timespan day = Timespan(1,0,0,0,0);
			DateTime tomarrow = today + day;
			Event* sayaEvent = new Event(tomarrow,"62",energyType,"sell",Saya->getEnergyStatus(energyType),
							   (market->getEnergyPrice(energyType))*0.9 );

			events->addEvent(sayaEvent);
			string message = "Saya generate selling event: "+ Reading::doubleToString(tomarrow.day())+"/"+
															  Reading::doubleToString(tomarrow.month())+"/"+
															  Reading::doubleToString(tomarrow.year())+ ", energy type: " + energyType
															  + ",number: " +
															  Reading::doubleToString(Saya->getEnergyStatus(energyType))+
															  " and price: "+Reading::doubleToString(market->getEnergyPrice(energyType)*0.9);

			CAppLogger::Instance().Log( message,Message::PRIO_WARNING);
		}
		else if(market->SayaCreateBuyingEvent(energyType))
		{
			Timespan day = Timespan(1,0,0,0,0);
			DateTime tomarrow = today + day;
			Event* sayaEvent = new Event(tomarrow,"62",energyType,"buy",sayaBuyingAmount,(market->getEnergyPrice(energyType))*1.05 );
			events->addEvent(sayaEvent);
			string message = "Saya generate buying event: "+ Reading::doubleToString(tomarrow.day())+"/"+
															  Reading::doubleToString(tomarrow.month())+"/"+
															  Reading::doubleToString(tomarrow.year())+", energy type: " + energyType
															  +", number: " +
															  Reading::doubleToString(sayaBuyingAmount) +
															  " and price: "+Reading::doubleToString(market->getEnergyPrice(energyType)*1.05);

			CAppLogger::Instance().Log( message,Message::PRIO_WARNING);

		}
	}
}

int SimulationWorkFlow::stringToInt(string str)
{
	istringstream buffer(str);
	int ans;
	buffer >> ans;
	return ans;
}


